Skip to Content

面试导航 - 程序员面试题库大全 | 前端后端面试真题 | 面试

在前面的内容我们已经知道了 JSX 实质上是一种语法糖,在编译过程中会通过 babel 或者 swc 等编译工具将其转移成 React.createElement 语法,它会返回一个 React 元素。

React 18 引入了 ReactDOM.createRoot 来创建根容器并渲染 React 元素,确保浏览器中的真实 DOM 数据与 React 元素保持一致。

createRoot 支持并发特性,通过 root.render 方法将虚拟 DOM 转换为真实 DOM,并插入到指定的容器中。

const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);

Babel 编译的产物

接下来,我们编写几个示例来看看 JSX 经过 Babel 编译后的具体形式。

首先,在我们的项目中有以下 React 元素:

<div class="box" id="content"> <div class="title">Hello</div> <button data="moment">Moment</button> <div> <div class="age">18</div> </div> </div>

首先,我们需要安装必要的 Babel 依赖包,以将代码转换为 React.createElement 形式:

pnpm add @babel/core @babel/preset-react -D
  1. @babel/core:这是 Babel 的核心库,负责将现代 JavaScript 代码转换为向后兼容的 JavaScript 代码,以便在旧版浏览器和环境中运行。

  2. @babel/preset-react:这是 Babel 的一个预设,用于转换 JSX 语法,使其能够被编译为 React.createElement 调用,从而在 React 应用中使用。

我们创建一个 index.js 文件并编写如下代码:

const babel = require('@babel/core'); // 定义JSX字符串 const jsxString = ` <div class="box" id="content"> <div class="title">Hello</div> <button data="moment">Moment</button> <div> <div class="age">18</div> </div> </div> `; const result = babel.transformSync(jsxString, { presets: ['@babel/preset-react'], }); // 输出编译后的代码 console.log(result.code);

在终端上执行 node index.js 之后会有如下输出:

React.createElement( 'div', { class: 'box', id: 'content', }, React.createElement( 'div', { class: 'title', }, 'Hello', ), React.createElement( 'button', { data: 'moment', }, 'Moment', ), React.createElement( 'div', null, React.createElement( 'div', { class: 'age', }, '18', ), ), );

由上面的编译结果我们可以知道,它的最外层 div 是通过 React.createElement 来创建的,而它的子节点,是会在它的第三个参数开始继续调用 React.createElement 来创建的,

但是话又说回来,当我们在源码的 createElement 函数中开启了 debugger 模式,但是好像没生效,那是什么回事呢?

发现浏览器并没有进入 debugger 断点调试,那是什么原因呢?

其实是 react17 引入了一个新的 jsx 函数用于处理 JSX 语法。它旨在优化编译后的代码并减少不必要的开销。两者的功能非常相似,都是用于创建 React 元素的,但 jsx 函数是 createElement 的一个优化版本。

在 React 源码,createElement 函数如下定义:

// src/react/packages/react/src/ReactElement.js export function createElement(type, config, children) { debugger; // 属性名称,用于后面的 for 循环 let propName; // 存储 React Element 中的普通元素属性,但是不包含 key ref self source const props = {}; // 懂的都懂 let key = null; // 懂的都懂 let ref = null; let self = null; let source = null; if (config != null) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { key = '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; for (propName in config) { if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } const childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { const childArray = Array(childrenLength); for (let i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; } // Resolve default props if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); }

而在 jsx 函数中,如下代码所示:

// src/react/packages/react/src/jsx/ReactJSXElement.js export function jsx(type, config, maybeKey) { let propName; const props = {}; let key = null; let ref = null; if (maybeKey !== undefined) { key = '' + maybeKey; } if (hasValidKey(config)) { key = '' + config.key; } if (hasValidRef(config)) { ref = config.ref; } // Remaining properties are added to a new props object for (propName in config) { if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } if (type && type.defaultProps) { const defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } return ReactElement(type, key, ref, undefined, undefined, ReactCurrentOwner.current, props); }

jsx 和 createElement 函数都用于创建 React 元素对象。jsx 通常用于将 JSX 语法编译成 React 元素,优化性能和代码大小,而 createElement 更常见于手动创建元素。它们都处理类型、配置、默认属性,并最终调用 ReactElement 返回新的 React 元素对象。两者都可以手动调用,但 jsx 更适合编译工具自动生成代码。

使用 babel 编译的时候,我们只需要添加一个参数即可编译成 jsx 函数:

最终输出结果如下所示:

jsx( 'div', { class: 'box', id: 'content', }, jsx( 'div', { class: 'title', }, 'Hello', ), jsx( 'button', { data: 'moment', }, 'Moment', ), jsx( 'div', null, jsx( 'div', { class: 'age', }, '18', ), ), );

通过对比前文,你会发现 jsx 函数会比 createElement 函数都是同样的结构,但是更简洁一点。

我们也可以直接在 React 项目中直接调用:

import * as React from 'react'; import { jsx } from 'react/jsx-runtime'; import * as ReactDOM from 'react-dom/client'; import reportWebVitals from './reportWebVitals'; function Moment() { return jsx('div', { className: 'moment', children: 'Hello, world!' }); } console.log(<Moment />); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<Moment />); reportWebVitals();

最终输出结果如下图所示:

和之前的一模一样。值得注意的是,开发阶段它不会调用这写函数,因为它会调用开发环境的函数:

总结

通过将 JSX 编译成 React.createElement 或 jsx 函数调用,React 不仅提高了开发体验和代码可读性,还通过优化后的 jsx 函数提升了性能。React 18 的新特性(如并发渲染)进一步增强了应用的性能和用户体验。

最后更新于:
Copyright © 2025Moment版权所有粤ICP备2025376666